package com.coincalf.wallet.task.syncblock;

import com.coincalf.dto.CoinInfoDto;
import com.coincalf.feign.CoinServiceClient;
import com.coincalf.framework.enums.RechargeStatus;
import com.coincalf.framework.utils.DateUtil;
import com.coincalf.wallet.blockchain.BlockConstant;
import com.coincalf.wallet.blockchain.BtcService;
import com.coincalf.wallet.blockchain.UsdtService;
import com.coincalf.wallet.entity.CoinRecharge;
import com.coincalf.wallet.entity.SyncBlockHeight;
import com.coincalf.wallet.entity.UserAddress;
import com.coincalf.wallet.service.CoinRechargeService;
import com.coincalf.wallet.service.SyncBlockHeightService;
import com.coincalf.wallet.service.UserAddressService;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import wf.bitcoin.javabitcoindrpcclient.BitcoindRpcClient;

import java.math.BigDecimal;
import java.util.*;

/**
 * 同步BTC以及USDT_OMNI最新区块任务
 */
@Component
@EnableScheduling
@Transactional(rollbackFor = Exception.class)
public class SyncBtcNewBlock {

    private static final Logger log = LoggerFactory.getLogger(SyncBtcNewBlock.class);

    private static final String NULL_DATA = "nulldata";//USDT_OMNI转账
    private static final String USDT_PREFIX = "6a146f6d6e69000000000000001f";//USDT_OMNI转账固定前缀

    @Autowired
    private UserAddressService userAddressService;
    @Autowired
    private CoinRechargeService rechargeService;
    @Autowired
    private SyncBlockHeightService syncBlockHeightService;
    @Autowired
    private CoinServiceClient coinServiceClient;

    /**
     * USDT区块扫描，扫描BTC以及USDT交易
     * USDT部分：根据区块高度获取所有交易哈希 -> 遍历区块哈希 -> 符合则交易写入
     * BTC部分：根据区块哈希取所有交易哈希 -> 遍历区块哈希 -> 符合则交易写入
     * <p>
     * 合二为一：
     */
    @Scheduled(cron = "0 0/5 * * * ?")
    public void task() {
        log.info("开始执行BTC区块扫描任务 当前时间={}", DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss"));
        long startTime = System.currentTimeMillis();
        //从数据库获取已经同步的区块高度
        SyncBlockHeight btcSyncedBlockHeight = syncBlockHeightService.selectOneByCoinName("BTC");
        if (btcSyncedBlockHeight == null || btcSyncedBlockHeight.getSyncedBlockHeight() == null) {
            log.warn("无法找到BTC同步缓存高度");
            return;
        } else if (btcSyncedBlockHeight.getStatus() == 0) {
            log.info("本次BTC同步状态已关闭");
            return;
        }
        int height = btcSyncedBlockHeight.getSyncedBlockHeight();
        int newestBlock = BtcService.getBlockCount();

        if (height >= newestBlock) {
            log.info("已同步BTC高度:{} 追上最新高度:{} 啦", height, newestBlock);
            return;
        }

        //rpc节点调用查询区块信息
        //同步区块
        String blockHash = BtcService.getBlockHash(height);
        BitcoindRpcClient.Block block = BtcService.getTxsHash(blockHash);
        int confirmations = block.confirmations();
        //出块时间
        Date blockTime = block.time();
        List<String> txsHash = block.tx();
        log.info("当前BTC区块所有交易哈希数量={}", txsHash.size());
        //获取所有用户BTC地址
        HashMap<String, Long> userAddress = Maps.newHashMap();

        //所有用户BTC地址
        List<UserAddress> userAddresses = userAddressService.selectListByCoinId(btcSyncedBlockHeight.getCoinId());
        for (UserAddress address : userAddresses) {
            String chainAddress = address.getAddress();
            userAddress.put(chainAddress, address.getUserId());
        }
        //查询交易详情
        for (String txHash : txsHash) {
            BitcoindRpcClient.RawTransaction rawTransaction = BtcService.getRawTransaction(txHash);
            BigDecimal txIn = BigDecimal.ZERO;//交易输入
            BigDecimal txOut = BigDecimal.ZERO;//交易输出
            //交易金额
            BigDecimal amount;

            //遍历交易输出,判断是否有目标用户地址
            //String targetAddress = null;
            Map<String, BigDecimal> targetAddresses = new HashMap<>(4);

            //遍历交易输出
            List<BitcoindRpcClient.RawTransaction.Out> outs = rawTransaction.vOut();
            //判断交易输出地址中是否存在用户地址
            for (BitcoindRpcClient.RawTransaction.Out out : outs) {
                //交易输出
                txOut = txOut.add(out.value());

                //首先判断是否是USDT_OMNI转账
                if (out.scriptPubKey().type().equals(SyncBtcNewBlock.NULL_DATA) && out.scriptPubKey().hex().startsWith(SyncBtcNewBlock.USDT_PREFIX)) {
                    //说明附带了一笔USDT_OMNI的转账交易 查询USDT_OMNI交易详情
                    Map<String, Object> usdtOmniTx = UsdtService.getTransaction(txHash);
                    String toAddress = usdtOmniTx.get(BlockConstant.TO_ADDRESS).toString();
                    //是否是app内地址
                    if (userAddress.containsKey(toAddress)) {
                        String fromAddress = usdtOmniTx.get(BlockConstant.FROM_ADDRESS).toString();
                        BigDecimal usdtOmniAmount = new BigDecimal(usdtOmniTx.get(BlockConstant.AMOUNT).toString());
                        Object feeObj = usdtOmniTx.get(BlockConstant.FEE);
                        BigDecimal fee = new BigDecimal(feeObj.toString());
                        //构建USDT_OMNI交易 并将交易纪录写入
                        //TODO 注意：USDT_OMNI的交易哈希多了usdt-omni-前缀，查询交易需要去除
                        String usdtOmni = "USDT_OMNI";
                        CoinInfoDto usdtOmniCoin = coinServiceClient.selectCoinInfo(usdtOmni);
                        this.addTx(txHash, usdtOmniCoin.getCoinId(), usdtOmni, fromAddress, toAddress, userAddress.get(toAddress), usdtOmniAmount, fee, confirmations, height, blockTime);
                    }
                }
                //如果是USDT_OMNI转账 这里是没有addresses的 所需需要非空判断
                List<String> addresses = out.scriptPubKey().addresses();
                if (!CollectionUtils.isEmpty(addresses)) {
                    String address = addresses.get(0);
                    //判断是否是app内地址
                    if (userAddress.containsKey(address)) {
                        log.info("查询到BTC交易 address={} amount={} txHash={}", address, out.value(), txHash);
                        //如果包含用户地址 记录该地址
                        //targetAddress = address;
                        amount = out.value();
                        targetAddresses.put(address, amount);
                    }
                }
            }

            //充币纪录写入
            if (!targetAddresses.isEmpty()) {
                StringBuilder fromAddress = new StringBuilder("");
                //遍历交易输入
                List<BitcoindRpcClient.RawTransaction.In> ins = rawTransaction.vIn();
                for (BitcoindRpcClient.RawTransaction.In in : ins) {
                    BitcoindRpcClient.RawTransaction.Out transactionOutput = in.getTransactionOutput();
                    txIn = txIn.add(transactionOutput.value());
                    List<String> addresses = in.getTransactionOutput().scriptPubKey().addresses();
                    //TODO BTC转账地址是无法确定具体哪一个的，可能存在多个转账地址 可以用英文逗号分隔开
                    fromAddress.append(addresses.get(0)).append(",");
                }
                //矿工费
                BigDecimal fee = txIn.subtract(txOut);

                Set<Map.Entry<String, BigDecimal>> entries = targetAddresses.entrySet();
                String toAddress;
                for (Map.Entry<String, BigDecimal> entry : entries) {
                    toAddress = entry.getKey();
                    amount = entry.getValue();
                    //构建BTC交易表
                    this.addTx(txHash, btcSyncedBlockHeight.getCoinId(), "BTC", fromAddress.toString(), toAddress, userAddress.get(toAddress), amount, fee, confirmations, height, blockTime);
                }
            }

            //下面只能处理单个地址充币
            /*if (StringUtils.isNotBlank(targetAddress)) {
                String fromAddress = "";
                //遍历交易输入
                List<BitcoindRpcClient.RawTransaction.In> ins = rawTransaction.vIn();
                for (BitcoindRpcClient.RawTransaction.In in : ins) {
                    BitcoindRpcClient.RawTransaction.Out transactionOutput = in.getTransactionOutput();
                    txIn = txIn.add(transactionOutput.value());
                    List<String> addresses = in.getTransactionOutput().scriptPubKey().addresses();
                    if (!targetAddress.equals(addresses.get(0))) {
                        fromAddress = addresses.get(0);
                    }
                }
                //矿工费
                BigDecimal fee = txIn.subtract(txOut);
                //构建BTC交易表
                this.addTx(txHash, btcSyncedBlockHeight.getCoinId(), "BTC", fromAddress, targetAddress, userAddress.get(targetAddress), amount, fee, confirmations, height, blockTime);
            }*/
        }

        //再次查询mysql最新数据 判断同步任务是否需要执行
        height = height + 1;
        btcSyncedBlockHeight.setSyncedBlockHeight(height);
        btcSyncedBlockHeight.setLastUpdateTime(new Date());
        syncBlockHeightService.updateById(btcSyncedBlockHeight);

        //任务结束
        long endTime = System.currentTimeMillis();
        long spent = (endTime - startTime) / 1000;
        log.info("本次BTC区块同步任务结束，耗时={}秒 height={}", spent, height);
    }

    private void addTx(
            String txHash,
            Long coinId,
            String coinName,
            String fromAddress,
            String toAddress,
            Long userId,
            BigDecimal amount,
            BigDecimal fee,
            long confirmations,
            long height,
            Date blockTime
    ) {

        //查询交易是否已经存在
        CoinRecharge recharge = rechargeService.selectOneByCoinIdAndTxIdAndToAddress(
                coinId,
                txHash,
                toAddress
        );
        if (recharge != null) {
            log.info("mysql已存在该{}充币交易，跳过啦!~ txId={}", coinName, txHash);
            return;
        }

        //构建交易表
        CoinRecharge coinRecharge = new CoinRecharge();
        coinRecharge.setCoinId(coinId);
        coinRecharge.setCoinName(coinName);
        coinRecharge.setFromAddress(fromAddress);
        coinRecharge.setAddress(toAddress);
        coinRecharge.setUserId(userId);
        coinRecharge.setTxid(txHash);
        coinRecharge.setMum(amount);
        coinRecharge.setFee(fee);
        coinRecharge.setNum(amount.add(fee));
        coinRecharge.setConfirm(Math.toIntExact(confirmations));
        coinRecharge.setBlockNumber(Math.toIntExact(height));
        coinRecharge.setStatus(RechargeStatus.PENDING.getCode());
        coinRecharge.setCollected(0);
        coinRecharge.setCreated(blockTime);
        coinRecharge.setLastUpdateTime(new Date());
        //写入数据库
        rechargeService.insert(coinRecharge);
        log.info("写入{}充币交易到数据库啦!~~ coinRecharge={}", coinName, coinRecharge);
    }

}
